package com.workingpub.commons.jdbc.builder;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.workingpub.commons.jdbc.builder.ognl.OgnlClassResolver;

import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;

/**
 * SQL绑定参数解析器
 * 
 * @author taoping
 *
 */
public class SqlParamBuilder {
	/**
	 * SQL解析时参数绑定正则式: #{}
	 */
	public static final String REGEX_PARAM_RESOLVE = "\\#\\{([\\w.]+)\\}";
	/**
	 * SQL执行时参数绑定正则式: ${}
	 */
	public static final String REGEX_PARAM_EXECUTE = "\\$\\{([\\w.]+)\\}";

	public static String build(String sql, BuildContext buildContext) {
		return build(sql, buildContext, null);
	}

	public static String build(String sql, BuildContext buildContext, Map<String, Object> others) {
		return build(sql, buildContext, others, null);
	}

	public static String build(String sql, BuildContext buildContext, Map<String, Object> others, Integer index) {
		Assert.isTrue(StringUtils.hasText(sql), "The sql text is null or empty.");
		OgnlContext context = (OgnlContext) Ognl.createDefaultContext(buildContext.getRootParameter(),
				new OgnlClassResolver());
		if (null != buildContext.getOtherParameters()) {
			context.putAll(buildContext.getOtherParameters());
		}
		if (null != others) {
			context.putAll(others);
		}
		String result = buildParamResolve(sql, buildContext, context, index);
		result = buildParamExecute(result, buildContext, context, index);
		return result;
	}

	private static String buildParamResolve(String sql, BuildContext buildContext, OgnlContext context, Integer index) {
		Pattern pattern = Pattern.compile(REGEX_PARAM_RESOLVE);
		Matcher matcher = pattern.matcher(sql);
		String result = null;
		while (matcher.find()) {
			// String source = matcher.group(0);
			String expression = matcher.group(1);
			try {
				String bindingParam = expression.replaceAll("\\.", "_");
				Object bindingValue = Ognl.getValue(expression, context, buildContext.getRootParameter());
				if (null != bindingValue && bindingValue.getClass().isEnum()) {
					try {
						Method method = bindingValue.getClass().getMethod("ordinal");
						bindingValue = method.invoke(bindingValue);
					} catch (Exception e) {
						Assert.isNull(e, "Failed to deal enum value : " + e.getMessage());
					}
				}
				buildContext.addSourceParam(bindingParam, bindingValue);
				String replacement = null == index ? ":" + bindingParam : ":" + bindingParam + "_" + index;
				result = matcher.replaceFirst(replacement);
			} catch (OgnlException e) {
				throw new IllegalArgumentException("OGNL exception : " + e.getMessage(), e);
			}
			matcher = pattern.matcher(result);
		}
		return result == null ? sql : result;
	}

	private static String buildParamExecute(String sql, BuildContext buildContext, OgnlContext context, Integer index) {
		Pattern pattern = Pattern.compile(REGEX_PARAM_EXECUTE);
		Matcher matcher = pattern.matcher(sql);
		String result = null;
		while (matcher.find()) {
			// String source = matcher.group(0);
			String expression = matcher.group(1);
			try {
				Object bindingValue = Ognl.getValue(expression, context, buildContext.getRootParameter());
				result = matcher.replaceFirst(String.valueOf(bindingValue));
			} catch (OgnlException e) {
				Assert.isNull(e, "OGNL exception : " + e.getMessage());
			}
			matcher = pattern.matcher(result);
		}
		return result == null ? sql : result;
	}
}